package prj4clientswing;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
//package lezer;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.PriorityQueue;
import java.util.Queue;
import javax.comm.CommPortIdentifier;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;

/**
 *
 * @author hagendijk
 */
public class MagTekTrackReader implements Runnable {

    public static final byte APP_TRANSPORT = (byte) 0x82;
    public static final byte APP_MAG_STRIPE = (byte) 0x01;
    public static final byte[] MSG_RESET_DEVICE = { (byte) 0x01, (byte) 0x00, (byte) 0x91, (byte) 0x00, (byte) 0x00, (byte) 0x90, (byte) 0x00};
    public static final byte[] MSG_BAUD_SYNC = { (byte) 0x01, (byte) 0x00, (byte) 0x96, (byte) 0x00, (byte) 0x02, (byte) 0x95, (byte) 0x4D, (byte) 0x54, (byte) 0x19};
    public static final byte[] MSG_GET_COMM_PARAMETERS = { (byte) 0x01, (byte) 0x00, (byte) 0x92, (byte) 0x00, (byte) 0x01, (byte) 0x92, (byte) 0x00, (byte) 0x00};
    public static final byte[] MSG_RESYNCHRONIZE_CMD = { (byte) 0x01, (byte) 0x00, (byte) 0x90, (byte) 0x00, (byte) 0x00, (byte) 0x91, (byte) 0x00};
    public static final byte[] MSG_RESYNCHRONIZE_RSP = { (byte) 0x01, (byte) 0x00, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0xA1, (byte) 0x00};
    Queue<byte[]> transmitQueue = new PriorityQueue<byte[]>();
    Queue<byte[]> responseQueue = new PriorityQueue<byte[]>();
    Queue<byte[]> notificationQueue = new PriorityQueue<byte[]>();

    public static String portName = "COM4";
    public CommPortIdentifier portId;
    public SerialPort serialPort;
    public InputStream inputStream;
    public OutputStream outputStream;
    public boolean open = false;

    public int baudrate = 9600;
    public int databits = SerialPort.DATABITS_8;
    public int stopbits = SerialPort.STOPBITS_1;
    public int parity = SerialPort.PARITY_EVEN;
    public int rtscts_in = SerialPort.FLOWCONTROL_NONE;
    public int rtscts_out = SerialPort.FLOWCONTROL_NONE;
    public int xonxoff_in = SerialPort.FLOWCONTROL_NONE;
    public int xonxoff_out = SerialPort.FLOWCONTROL_NONE;

    public boolean debug = false;

    public boolean consumed = false;

    public MagTekTrackReader() {
        try
        {
          System.out.println("LOG 1 " + portName);
            portId = CommPortIdentifier.getPortIdentifier(portName);
        //    portId = CommPortIdentifier.
            serialPort = (SerialPort) portId.open("MagTekReader ", 2000);
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();
            open = true;
            serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);
            serialPort.setFlowControlMode(rtscts_in | rtscts_out | xonxoff_in | xonxoff_out);
            serialPort.setDTR(true);
            serialPort.notifyOnDataAvailable(true);
            Thread transmitting = new Thread(this, "MagTekApduPCO");
            transmitting.start();

        } catch (Exception e)
        {
          System.out.println("FOUT: " + e);
            //e.printStackTrace(); System.out.println(e);
        }
    }

    public String readTrack(byte n) {
        byte[] tx = new byte[5];
        tx[0] = (byte)0x00;
        tx[1] = APP_MAG_STRIPE;
        tx[2] = (byte)0x82;
        tx[3] = (byte)0x00;
        tx[4] = n;
        byte[] rx = transmit(tx);
        try{
            Thread.sleep(100);
        }catch (Exception err){}
        try {
            switch (rx[3]) {
                case 0x00:
                    return new String(rx);
                case 0x06:
                    return "Bad track number";
                default:
                    return "Power up of ICC failed";
            }
        } catch (Exception e) {}
        return "null";
    }

    public boolean consumeCard() {
        System.out.println("consumeCard");
        byte[] txs = new byte[4];
        txs[0] = (byte) 0x00;
        txs[1] = APP_TRANSPORT;
        txs[2] = (byte) 0x80;
        txs[3] = (byte) 0x00;
        byte[] rx = transmit(txs);
        try{
            Thread.sleep(100);
        }catch (Exception err){}
        try {
            switch (rx[3]) {
                case 0x00:{
                    consumed = true;
                    return true;
                }
                default:
                    System.out.println("Unable to consume card");
                    return false;
            }
        } catch (Exception e) {System.out.println("not consumed" + e); return false;}
    }

    public void ejectCard() {
        byte[] txs = new byte[4];
        txs[0] = (byte) 0x00;
        txs[1] = APP_TRANSPORT;
        txs[2] = (byte) 0x81;
        txs[3] = (byte) 0x00;
        byte[] rx = transmit(txs);
        try{
            switch (rx[3]) {
                case 0x00:
                    consumed = false;
                    break;
                default:
                    System.out.println("Unable to eject card");
            }
        } catch (Exception e) {System.out.println("erro?"+e); }
    }

    public byte[] transmit(byte[] data) {
       // System.out.println("transmit " +data);
        transmitQueue.add(data);
        synchronized (this) {
            notify();
        }
        long stoptime = System.currentTimeMillis() + 1500;
        do {
            try {
                byte[] response = responseQueue.remove();
                byte[] rx = response;
                if (rx[1] == data[1] && rx[2] == data[2]) {
                    return rx;
                }
            } catch (Exception e) {
//e.printStackTrace(); System.out.println(e);
            }
        } while (System.currentTimeMillis() < stoptime);
        //System.out.println("Unable to send data to card");
        return null;
    }
    public boolean interrupted = false;

    public void interrupt() {
        interrupted = true;
    }

    public static final int BWT = 250;
    public static final byte STATE_SA = 0x01;
    public static final byte STATE_DA = 0x02;
    public static final byte STATE_PCB = 0x03;
    public static final byte STATE_LEN1 = 0x04;
    public static final byte STATE_LEN2 = 0x05;
    public static final byte STATE_HEDC = 0x06;
    public static final byte STATE_DATA = 0x07;
    public static final byte STATE_FEDC = 0x08;
    public int state = STATE_SA;
    public static final byte PSTATE_SYNC = 0; // baud sync
    public static final byte PSTATE_INIT = 1; // resync
    public static final byte PSTATE_CONN = 2; // connected
    public int pstate = PSTATE_SYNC;
    public long rxacktime = 0;
    public long txacktime = 0;
    public boolean ack = false;
    public boolean poll = false;
    public boolean message = false;
    public boolean iFrameOutstanding = false;
    public byte iFrameOutstandingPcb = 0x00;
    public boolean sFrameOutstanding = false;
    public byte sFrameOutstandingPcb = 0x00;
    public int trials = 0;
    public byte[] tx = new byte[5];
    public int length = 0;
    public int checksum = 0;
    public int read = 0;
    public int errors = 0;
    public int txSequenceNumber = 0;
    public int rxSequenceNumber = 0;
    public byte pcb;
    public byte[] buffer = new byte[512];

    public void run() {
        try {
            long time = System.currentTimeMillis();
            long lastRx = time;
            while (!interrupted || ack || poll) {
                synchronized (this) {
                    message = false;
                    time = System.currentTimeMillis();
                    if (inputStream.available() > 0) {
                        lastRx = time;
                    } else if (state != STATE_SA && (time - lastRx) > 3000) {
                        sendError(pcb, (byte) 0x02);
                        ack = false;
                        poll = false;
                        state = STATE_SA;
                    }
                    while (inputStream.available() > 0 && !message) {
                        byte rx = (byte) inputStream.read();
//switch ((byte)(Math.random()*100))
                        {
//case 0: break; // lost byte
//case 1: rx ^= 0xFF; break; // corrupted byte
//default:
                            if (debug) {
                                System.out.println(state + " " + System.currentTimeMillis() + " < " + rx);
                            }
                            switch (state) {
                                case STATE_SA:
                                    checksum = rx;
                                    state = STATE_DA;
                                    break;
                                case STATE_DA:
                                    checksum ^= rx;
                                    state = STATE_PCB;
                                    break;
                                case STATE_PCB:
                                    checksum ^= rx;
                                    pcb = rx;
                                    state = STATE_LEN1;
                                    break;
                                case STATE_LEN1:
                                    checksum ^= rx;
                                    length = rx & 0xff;
                                    length *= 256;
                                    state = STATE_LEN2;
                                    break;
                                case STATE_LEN2:
                                    checksum ^= rx;
                                    length += rx & 0xff;
                                    if (length > buffer.length) {
                                        buffer = new byte[length];
                                    }
                                    read = 0;
                                    state = STATE_HEDC;
                                    break;
                                case STATE_HEDC:
                                    if (rx != checksum) // System.out.println("HEDC error");
                                    {
                                        while (inputStream.available() > 0) {
                                            rx = (byte) inputStream.read();
                                            if (debug) {
                                                System.out.println(state + " " + System.currentTimeMillis() + " < " + rx);
                                            }
                                        }
                                        sendError(pcb, (byte) 0x01);
                                        state = STATE_SA;
                                    } else {
                                        checksum ^= rx;
                                        if (length > 0) {
                                            state = STATE_DATA;
                                        } else {
                                            state = STATE_FEDC;
                                        }
//switch (pcb & 0xC0)
//{
//case 0x00: // I-Frame
// if (iFrameOutstanding && txSequenceNumber != (pcb & 0x01)) // rule 4.8
// {
// txSequenceNumber = (txSequenceNumber + 1) % 2;
// iFrameOutstanding = false; // rule 4.9a
// transmitQueue.remove();
// errors = 0;
// }
// break;
//}
                                    }
                                    break;
                                case STATE_DATA:
                                    checksum ^= rx;
                                    buffer[read++] = rx;
                                    if (read == length) {
                                        state = STATE_FEDC;
                                    }
                                    break;
                                case STATE_FEDC:
                                    if (rx == checksum) {
                                        switch (pcb & 0xC0) {
                                            case 0x00: // I-Frame
                                                if ((pcb & 0x04) != 0x00) {
                                                    sendReject(pcb, (byte) 0x02); // reject frame - chaining not supported
                                                    break;
                                                }
                                                if (rxSequenceNumber == ((pcb & 0x02) >> 1)) // rule 4.4, 4.5
                                                {
                                                    rxSequenceNumber = (rxSequenceNumber + 1) % 2; switch (buffer[0]) {
                                                        case (byte) 0x40:
                                                            responseQueue.add(buffer);
                                                            break;
                                                        case (byte) 0x80:
                                                            notificationQueue.add(buffer);
                                                            break;
                                                    }
                                                }
                                                if (iFrameOutstanding && txSequenceNumber != (pcb & 0x01)) // rule 4.8
                                                {
                                                    txSequenceNumber = (txSequenceNumber + 1) % 2;
                                                    iFrameOutstanding = false; // rule 4.9a
                                                    transmitQueue.remove();
                                                    errors = 0;
                                                }
                                                ack = true; // ack with R-Frame
                                                rxacktime = System.currentTimeMillis() + 200;//100;//BWT;
                                                pstate = PSTATE_CONN;
                                                break;
                                            case 0x80: // S-Frame
                                                switch (pcb) {
                                                    case (byte) 0x85: // frame reject command (indication)
                                                        break;
                                                    case (byte) 0x88: // frame resend command (indication)
                                                        if (iFrameOutstanding && iFrameOutstandingPcb == buffer[7]) // rule 6.2b error recovery
                                                        {
                                                            iFrameOutstanding = false;
                                                            errors++;
                                                            if (errors > 2) {
                                                                pstate = PSTATE_INIT;
                                                            }
                                                        }
//else if (sFrameOutstanding && sFrameOutstandingPcb == buffer[7]); // rule 6.3a ignore
                                                        break;
                                                    case (byte) 0x90: // resynchronise protocol command
                                                        if (debug) {
                                                            System.out.println("resync");
                                                        }
                                                        sendMessage(MSG_RESYNCHRONIZE_RSP);
                                                        sFrameOutstandingPcb = MSG_RESYNCHRONIZE_RSP[2];
                                                        txSequenceNumber = 0;
                                                        rxSequenceNumber = 0;
                                                        ack = false;
                                                        poll = false;
                                                        errors = 0;
                                                        iFrameOutstanding = false;
                                                        sFrameOutstanding = false;
                                                        break;
                                                    case (byte) 0x97: // echo command
                                                        if (debug) {
                                                            System.out.println("echo");
                                                        }
                                                        int i = 0;
                                                        tx[i++] = (byte) 0x01;
                                                        tx[i++] = (byte) 0x00;
                                                        tx[i++] = (byte) 0xA7;
                                                        tx[i++] = (byte) (length / 256);
                                                        tx[i++] = (byte) (length % 256);
                                                        sendMessageWithChecksum(tx, i);
                                                        sendMessageWithChecksum(buffer, length);
                                                        sFrameOutstandingPcb = tx[2];
                                                        break;
                                                    case (byte) 0xA0: // resynchronise protocol response
                                                        sFrameOutstanding = false;
                                                        pstate = PSTATE_CONN;
                                                        break;
                                                    case (byte) 0xA6: // baud sync response
                                                        sFrameOutstanding = false;
                                                        pstate = PSTATE_INIT;
                                                        break;
                                                    case (byte) 0x91: // reset device command
                                                    case (byte) 0x92: // get communication parameters command
                                                    case (byte) 0x96: // baud sync command
                                                    default:
                                                        sendReject(pcb, (byte) 0x01); // unsupported S-frame command
                                                }
                                            case 0xC0: // R-Frame
                                                if ((pcb & 0x20) == 0x20) {
                                                    poll = true;
                                                } // send R-Frame
                                                if (iFrameOutstanding && txSequenceNumber != (pcb & 0x01)) // rule 4.8
                                                {
                                                    txSequenceNumber = (txSequenceNumber + 1) % 2;
                                                    iFrameOutstanding = false; // rule 4.9a
                                                    transmitQueue.remove();
                                                }
                                                break;
                                            default: // Unsupported frame type
//System.out.println("Unsupported frame type"); // rule 6.4a ignore unsupported frame type
                                                sendReject(pcb, (byte) 0x00); // rule 6.4b reject unsupported frame type
                                                break;
                                        }
                                        message = true;
                                    } else // rule 6.1a ignore frame EDC error
                                    {
                                        while (inputStream.available() > 0) {
                                            rx = (byte) inputStream.read();
                                            if (debug) {
                                                System.out.println(state + " " + System.currentTimeMillis() + " < " + rx);
                                            }
                                        }
                                        sendError(pcb, (byte) 0x01); // 0x01 = EDC or parity error
                                        ack = false;
                                        poll = false;
                                    }
                                    state = STATE_SA;
                                    break;
                            }
                        }
                    }
                    time = System.currentTimeMillis();
                    switch (pstate) {
                        case PSTATE_SYNC:
                            if (!sFrameOutstanding) {
                                trials = 0;
                            } else if (time > txacktime) // timeout
                            {
                                sFrameOutstanding = false;
                                if (debug) {
                                    System.out.println("timeout");
                                }
                                trials++;
                                if (trials > 25) {
                                    interrupt();
                                }
                            }
                            if (!sFrameOutstanding) {
                                if (debug) {
                                    System.out.println("baudrate sync");
                                }
                                sendMessage(MSG_BAUD_SYNC);
                                txacktime = System.currentTimeMillis() + 100;
                                sFrameOutstandingPcb = MSG_BAUD_SYNC[2];
                                sFrameOutstanding = true;
                            }
                            break;
                        case PSTATE_INIT:
                            if (!sFrameOutstanding) {
                                trials = 0;
                                txSequenceNumber = 0;
                                rxSequenceNumber = 0;
                                ack = false;
                                poll = false;
                                iFrameOutstanding = false; // rule 4.9c
                            } else if (time > txacktime) // timeout
                            {
                                sFrameOutstanding = false;
                                if (debug) {
                                    System.out.println("timeout");
                                }
                                errors++;
                                if (errors > 1) {
                                    pstate = PSTATE_SYNC;
                                }
                            }
                            if (!sFrameOutstanding) {
                                if (debug) {
                                    System.out.println("resync");
                                }
                                sendMessage(MSG_RESYNCHRONIZE_CMD);
                                txacktime = System.currentTimeMillis() + BWT;
                                sFrameOutstandingPcb = MSG_RESYNCHRONIZE_CMD[2];
                                sFrameOutstanding = true;
                            }
                            break;
                        case PSTATE_CONN:
                        default:
                            if (iFrameOutstanding && time > txacktime) // timeout
                            {
                                if (debug) {
                                    System.out.println("timeout");
                                }
                                iFrameOutstanding = false; // rule 4.9b
                                errors++;
                                if (errors > 4) {
                                    pstate = PSTATE_INIT;
                                }
                            }
                            if (!iFrameOutstanding && !transmitQueue.isEmpty()) {
                                if (debug) {
                                    System.out.println("send i-frame");
                                }
                                byte[] data = (byte[]) transmitQueue.peek();
                                int i = 0;
                                tx[i++] = (byte) 0x01;
                                tx[i++] = (byte) 0x00;
                                tx[i++] = (byte) (0x20 ^ (2 * txSequenceNumber) ^ rxSequenceNumber);
                                tx[i++] = (byte) (data.length / 256);
                                tx[i++] = (byte) (data.length % 256);
                                sendMessageWithChecksum(tx, i);
                                sendMessageWithChecksum(data, data.length);
                                txacktime = System.currentTimeMillis() + BWT;
                                iFrameOutstanding = true;
                                iFrameOutstandingPcb = tx[2];
                                ack = false;
                                poll = false;
                            } else if (poll || (ack && time > rxacktime)) // rule 5.4a
                            {
                                if (debug) {
                                    System.out.println("send ack");
                                }
                                int i = 0;
                                tx[i++] = (byte) 0x01;
                                tx[i++] = (byte) 0x00;
                                tx[i++] = (byte) (0xC0 ^ rxSequenceNumber);
                                tx[i++] = (byte) 0x00;
                                tx[i++] = (byte) 0x00;
                                sendMessageWithChecksum(tx, i);
                                sendMessageWithChecksum(tx, 0);
                                sFrameOutstandingPcb = tx[2];
                                sFrameOutstanding = true;
                                ack = false;
                                poll = false;
                            }
                    }
                    time = System.currentTimeMillis();
                    long nextwakeup = time + 1000;
                    if (ack && rxacktime < nextwakeup) {
                        nextwakeup = rxacktime;
                    }
                    if ((sFrameOutstanding || iFrameOutstanding) && txacktime < nextwakeup) {
                        nextwakeup = txacktime;
                    }
                    long waittime = 10 * ((nextwakeup - time) / 10);
                    if (waittime <= 0) {
                        waittime = 10;
                    }
                    try {
                        wait(waittime);
                    } catch (InterruptedException e) {
                    }
                }
            }
        } catch (Exception e) {
//responseQueue.disable(new Exception("Could not receive data from serial port; " + e.getMessage()));
//notificationQueue.disable(new Exception("Could not receive data from serial port; " + e.getMessage()));
        }

    }
/*
0 RFU
1 EDC or Parity Error
2 Character Wait Timeout Error
 */
    public void sendError(byte pcb, byte reason) {
        if (debug) {
            System.out.println("error");
        }
        int i = 0;
        tx[i++] = (byte) 0x01;
        tx[i++] = (byte) 0x00;
        tx[i++] = (byte) 0x88;
        tx[i++] = (byte) 0x00;
        tx[i++] = (byte) 0x02;
        sendMessageWithChecksum(tx, i);
        i = 0;
        tx[i++] = pcb;
        tx[i++] = reason;
        sendMessageWithChecksum(tx, i);
        sFrameOutstandingPcb = tx[2];
        sFrameOutstanding = true;
    }

    public void sendMessageWithChecksum(byte[] msg, int length) {
        if (debug) {
            System.out.println(pstate + " " + System.currentTimeMillis() + " > " + msg);
        }
        try {
            byte checksum = 0;
            for (int i = 0; i < length; i++) {
                outputStream.write(msg[i]);
                checksum ^= msg[i];
            }
            outputStream.write(checksum);
            if (debug) {
                System.out.println(pstate + " " + System.currentTimeMillis() + " > " + checksum);
            }
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace(); System.out.println(e);
        }
    }

    public void sendReject(byte pcb, byte reason) {
        if (debug) {
            System.out.println("reject");
        }
        int i = 0;
        tx[i++] = (byte) 0x01;
        tx[i++] = (byte) 0x00;
        tx[i++] = (byte) 0x85;
        tx[i++] = (byte) 0x00;
        tx[i++] = (byte) 0x02;
        sendMessageWithChecksum(tx, i);
        i = 0;
        tx[i++] = pcb;
        tx[i++] = reason;
        sendMessageWithChecksum(tx, i);
        sFrameOutstandingPcb = tx[2];
        sFrameOutstanding = true;
    }

    public void sendMessage(byte[] msg) {
        if (debug) {
            System.out.println(pstate + " " + System.currentTimeMillis() + " > " + msg);
        }
        try {
            outputStream.write(msg);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace(); System.out.println(e);
        }
    }

    public static void main(String[] args) {
        MagTekTrackReader mtcr = new MagTekTrackReader();
        try{
            mtcr.ejectCard();
            while(!mtcr.consumed) {
                System.out.println("Insert card plz");
                //Thread.sleep(1000);
                mtcr.consumeCard();
                Thread.sleep(1000);
            }
            String track3="null";
            while (track3.equals("null"))track3=mtcr.readTrack((byte)0x03);
            System.out.println("track 3 " + track3);
            String track2="null";
            while (track2.equals("null"))track2=mtcr.readTrack((byte)0x02);
            System.out.println("track 2 " + track2);
            String track1="null";
            while (track1.equals("null")) track1=mtcr.readTrack((byte)0x01);
            System.out.println("track 1 " +track1);




            mtcr.ejectCard();
        }catch(Exception e){}
        System.out.println("--- THE END ---");
        System.exit(0);
    }
}
